/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *
 *
 * Created by hattori <hattori@banana-systems.com>
 *                    <hattori@apache.org> (English Only)
 *
 *
 */


#include "oauth_helper.h"
#include "oauth.h"
#include "http_log.h"

#define strEQ(str1,str2)   (strcmp(str1,str2) == 0)
#define strcEQ(str1,str2)  (strcasecmp(str1,str2) == 0)

static char *create_signature_base_string(request_rec *r, const char *method, char *url, apr_port_t port, int num_query_params, apr_hash_t *query_params);

const int maos_populate_signed_request_rec(request_rec *r, int num_query_params, apr_hash_t *query_params, signed_request_rec *sr){
    
    auth_opensocial_config_rec *conf;
    char **params;
    apr_array_header_t *arr = NULL;
    const char *oauth_signature_method = NULL;
    apr_hash_index_t *index;
    apr_ssize_t klen;
    char *key, *val;
    int k,i;

    conf = (auth_opensocial_config_rec *)
        ap_get_module_config(r->per_dir_config, &auth_opensocial_module);

    sr->num_query_params = num_query_params;
    sr->query_params = query_params;

    // sr->port is not used...
    sr->port = ap_get_server_port(r);

    // Required oauth parameters.
    // (http://oauth.net/core/1.0/#rfc.section.6.1.1)
    sr->oauth_consumer_key = NULL;
    sr->oauth_token = NULL;
    sr->oauth_signature_method = NULL;
    sr->oauth_timestamp = NULL;
    sr->oauth_nonce = NULL;
    sr->oauth_version = (char *)apr_pstrdup(r->pool, "1.0");
    sr->oauth_signature = NULL;

    for (index = apr_hash_first(r->pool, query_params);
                              index != NULL;
                              index = apr_hash_next(index)){

        apr_hash_this(index,
                   (const void **)&key,
                   &klen,
                   (void **)&arr);

        if(key[0] == 'o'){
            if ((klen >= 6) && (strstr(key, "oauth_") == key)){
                // this key is an OAuth parameter.

                if ( arr->nelts >= 2 ){
                    // ERROR : Duplicated OAuth Protocol Parameter 
                    // http://oauth.net/core/1.0/#rfc.section.10
                    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_auth_opensocial : Duplicated oauth params for %s", key);
                    return HTTP_BAD_REQUEST;
                }else if (klen >= 7){
                    switch(key[6]){
                        case 'c':
                            if(klen == 18 && strEQ(&key[6],"consumer_key")){
                                sr->oauth_consumer_key = (char *)((const char**)arr->elts)[0]; 
                            }
                            break; 
                        case 't':
                            if(klen == 11 && strEQ(&key[6],"token")){
                                sr->oauth_token = (char *)((const char**)arr->elts)[0]; 
                            }else if(klen == 15 && strEQ(&key[6],"timestamp")){
                                sr->oauth_timestamp = (char *)((const char**)arr->elts)[0];
                            }
                            break; 
                        case 's':
                            if(klen == 15 && strEQ(&key[6],"signature")){
                                sr->oauth_signature = (char *)((const char**)arr->elts)[0]; 
                            }else if(klen == 22 && strEQ(&key[6],"signature_method")){
                                sr->oauth_signature_method = (char *)((const char**)arr->elts)[0];
                            }
                            break; 
                        case 'n':
                            if(klen == 11 && strEQ(&key[6],"nonce")){
                                sr->oauth_nonce = (char *)((const char**)arr->elts)[0]; 
                            }
                            break; 
                        case 'v':
                            if(klen == 13 && strEQ(&key[6],"version")){
                                sr->oauth_version = (char *)((const char**)arr->elts)[0]; 
                            }
                            break; 
                    }
                }
            }
        }
    }


    /*
       null check on the required oauth parameters.
       OAuth requires the following parameters according to its spec,
       but opensocial doesn't...
       Actually, orkut doesn't send oauth_token in a request so 
       we don't check oauth_token. 
    */
    if ( 
         sr->oauth_consumer_key == NULL ||
         // sr->oauth_token == NULL ||
         sr->oauth_signature_method == NULL ||
         sr->oauth_timestamp == NULL ||
         sr->oauth_nonce == NULL ||
         sr->oauth_version == NULL ||
         sr->oauth_signature == NULL ){

         // ERROR : Missing required parameter
         // http://oauth.net/core/1.0/#rfc.section.10
         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_auth_opensocial : Missing required oauth params");
         return HTTP_BAD_REQUEST;
    } 

    if (!strcEQ(sr->oauth_signature_method,"HMAC-SHA1") && !strcEQ(sr->oauth_signature_method,"RSA-SHA1")){
        // ERROR : Unsupported signature method
        // http://oauth.net/core/1.0/#rfc.section.10
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_auth_opensocial : Unsupported OAuth signature method : oauth_signature_method : %s", sr->oauth_signature_method);
        return HTTP_BAD_REQUEST;
    }

    sr->sbs = create_signature_base_string(r, r->method, sr->url, sr->port , num_query_params, query_params);

    if(conf->debug_log_enabled){
        ap_log_rerror(APLOG_MARK,APLOG_DEBUG,0,r,"mod_auth_opensocial : signature base string : [%s]",sr->sbs);
    }

    return OK;
}

static char *create_signature_base_string(request_rec *r, const char *method, char *url, apr_port_t port, int num_query_params, apr_hash_t *query_params){

  char **params;
  apr_status_t status;
  apr_hash_index_t *index;
  apr_array_header_t *arr;
  apr_ssize_t klen;
  char *key, *val, *query_params_in_str = NULL;
  char *query,*sbs, *sbs_tmp;
  int k,i = 0;

  /*
  // increment to make a space of url in argv.
  num_query_params++;
  // decrement to remove a space of oauth_signature from argv 
  //  since oauth_signature param must NOT be included in the signature base string. 
  num_query_params--;
  */

  params = (char **)apr_palloc(r->pool, sizeof(char *)*(num_query_params));

  params[i++] = (char *)url;

  for (index = apr_hash_first(r->pool, query_params);
      index != NULL;
      index = apr_hash_next(index)){

    apr_hash_this(index,
                   (const void **)&key,
                   &klen,
                   (void **)&arr);

    if ( strcmp(key,"oauth_signature") != 0 ){
      // add this param to the signature base string except oauth_signature.
      for (k = 0; k < arr->nelts; k++) {
        const char *val = ((const char**)arr->elts)[k];
        params[i++] = (char *)apr_pstrcat(r->pool,key,"=",val,NULL);
      }
    }
  }

  // sort parameters
  qsort(&params[1], num_query_params-1, sizeof(char *), oauth_cmpstringp);

  // serialize URL
  query = oauth_serialize_url_parameters(num_query_params, params);

  sbs_tmp = oauth_catenc(3, method, params[0], query);
  free(query);
  sbs = apr_pstrdup(r->pool, sbs_tmp);
  free(sbs_tmp);

  return sbs;
}


const int maos_verify_signature(request_rec *r, signed_request_rec *sr){

    auth_opensocial_config_rec *conf;
    char *hash_key, *sign;
    char *hash_key_tmp, *sign_tmp;

    conf = (auth_opensocial_config_rec *)
        ap_get_module_config(r->per_dir_config, &auth_opensocial_module);

    hash_key_tmp = oauth_catenc(2, conf->consumer_secret, conf->token_secret);
    hash_key = apr_pstrdup(r->pool, hash_key_tmp);
    free(hash_key_tmp);

    if ( strcEQ(sr->oauth_signature_method,"HMAC-SHA1") && conf->hmac_sha1 ){
        if(!strEQ(sr->oauth_consumer_key, conf->consumer_key)){
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_auth_opensocial : consumer key in server and request didn't match for HMAC-SHA1 auth");
            return HTTP_UNAUTHORIZED;
        }
        sign_tmp = oauth_sign_hmac_sha1(sr->sbs,hash_key);
        sign = apr_pstrdup(r->pool, sign_tmp);
        free(sign_tmp);
        if(strEQ(sr->oauth_signature, sign)){
           return OK; 
        }else{
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_auth_opensocial : HMAC-SHA1 auth failed");
        }
    }else if ( strcEQ(sr->oauth_signature_method,"RSA-SHA1") && conf->rsa_sha1 ){
        if(oauth_verify_rsa_sha1(sr->sbs, conf->rsa_publickey,sr->oauth_signature)){
           return OK;
        }else{
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_auth_opensocial : RSA-SHA1 auth failed");
        }
    }else{
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_auth_opensocial : "
                      "User requested [%s] auth type "
                      "but server is configured not to accept it"
                      , sr->oauth_signature_method);
    }

    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_auth_opensocial : Authentication Failed");

    return HTTP_UNAUTHORIZED;
}
